#include "optimizer_adapter/to_pg_plan.h"

#include "access/attnum.h"
#include "planner/group_expression.h"
#include "planner/optimizer_context.h"
#include "planner/physical_operators.h"

extern "C" {
#include <postgres.h>

#include <nodes/makefuncs.h>
#include <nodes/nodes.h>
#include <nodes/pg_list.h>
#include <nodes/plannodes.h>
#include <optimizer/pathnode.h>
}

namespace optimizer {

PlanGenerator::PlanGenerator(OptimizerContext *ctx) : optimizer_context_(ctx) {}

PlanResult PlanGenerator::GeneratePlan(group_id_t id, PropertySet *required_props) {
  auto *group = optimizer_context_->GetMemo().GetGroupByID(id);
  PlanGeneratorContext ctx{
      .required_props_ = required_props,
      .group_ = group,
      .gexpr_ = group->GetBestExpression(required_props),
  };
  auto *plan = GeneratePlanInternal(&ctx);
  return PlanResult{.plan_ = plan, .rtable_ = rtable_, .relation_oids_ = relation_oids_};
}

Plan *PlanGenerator::GeneratePlanInternal(PlanGeneratorContext *ctx) {
  CASCADES_ASSERT(ctx->gexpr_ && ctx->required_props_, "Best Group Expr must not be null");
  switch (ctx->gexpr_->Contents()->GetOpType()) {
    case OperatorType::PhysicalSeqScan:
      return GenerateSeqScanPlan(ctx);

    default:
      throw std::runtime_error("Unsupported operation type for plan generation");
  }

  return nullptr;
}

/*
Generate a SeqScan plan node for the given group expression.
* if rte_ is null, this is a dummy scan node, gennerate a dummy scan node
*/
Plan *PlanGenerator::GenerateSeqScanPlan(PlanGeneratorContext *ctx) {
  const auto &seqscan = ctx->gexpr_->Contents()->CastTo<PhysicalSeqScan>();

  /*
  generate a dummy scan node
    pg101: select 1;  最终plan 中的rtable是无用的， 直接为 null 即可，最终只需要输出一个 plan 即可
  */
  if (seqscan.GetRangeTblEntry() == nullptr) {
    Result *result = makeNode(Result);
    result->plan.plan_node_id = GetNextPlanNodeID();
    result->plan.targetlist = GenerateTargetList(ctx->group_->GetGroupTarget());
    ApplyStatisticalProperties(&result->plan, ctx);
    return &result->plan;
  }

  rtable_ = lappend(rtable_, seqscan.GetRangeTblEntry());
  relation_oids_ = lappend_oid(relation_oids_, seqscan.GetRangeTblEntry()->relid);
  SeqScan *seq_scan = makeNode(SeqScan);
  seq_scan->scan.scanrelid = list_length(rtable_);
  auto *plan = &(seq_scan->scan.plan);
  plan->plan_node_id = GetNextPlanNodeID();
  plan->targetlist = GenerateTargetList(ctx->group_->GetGroupTarget());
  ApplyStatisticalProperties(plan, ctx);
  return plan;
}

void PlanGenerator::ApplyStatisticalProperties(Plan *plan, PlanGeneratorContext *ctx) {
  auto stats = ctx->group_->GetGroupStats();
  auto [cost, _] = ctx->group_->GetBestExpressionWithCost(ctx->required_props_);

  plan->startup_cost = 0;
  plan->total_cost = cost;
  plan->plan_rows = stats.node_rows_;
  plan->plan_width = stats.tuple_width_;
}

List *PlanGenerator::GenerateTargetList(PathTarget *tlist) {
  List *target_list = NIL;
  AttrNumber idx = 1;
  foreach_node(Expr, e, tlist->exprs) {
    target_list = lappend(target_list, makeTargetEntry((Expr *)e, idx++, nullptr, false));
  }
  return target_list;
}

}  // namespace optimizer
